package com.jiaai.shop.util.redis.aop;

import com.jiaai.shop.util.redis.RedisUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.util.Date;

/**
 * class_name:RedisLockAspect
 * describe:------------------>redis数据锁的切面类
 * create_user:张昉平
 * create_date:2018/2/7
 * create_time:12:03
 */
@Component
@Aspect
public class RedisLockAspect {

    private static final Logger xlog = LoggerFactory.getLogger(RedisLockAspect.class);

    @Autowired
    private RedisUtil redisUtil;

    @Before("@annotation(locker)")
    public void before(JoinPoint jp, RedisLocker locker) throws InterruptedException {
        //索引
        int index = locker.index();
        //有效时间,秒数
        int seconds = locker.expiredSecond();
        //参数
        Object[] objs = jp.getArgs();
        //redis的key
        String key = ""+objs[index];
        boolean flag = setnx(key, seconds);

        Date start = new Date();

        if (!flag){
            while (true){
                String curString = getString(key);
                long currValue = Long.valueOf( curString == null? "0":curString);//当前key对应的值
                if (currValue > new Date().getTime()){
                    Thread.sleep(500);//锁还未释放
                }else {
                    //获取锁
                    long res = getSet(key, seconds);
                    if (currValue == res){//只有一个线程能拿到currValue, 就是获得锁了
                        break;
                    }
                    //还是没有拿到锁, 继续循环
                }

                if (start.compareTo(new Date()) > 10000){
                    //防止jedis 错误而导致的线程死循环
                    throw new RuntimeException("redis locker expired");
                }
            }
        }
        //拿到锁了//一天后, 我表示我都不记得这写得啥.../黑人问号???
    }

    @After("@annotation(locker)")
    public void after(JoinPoint jp, RedisLocker locker){
        //索引
        int index = locker.index();
        //参数
        Object[] objs = jp.getArgs();
        //redis的key
        String key = ""+objs[index];

        String curString = getString(key);
        long currValue = Long.valueOf(curString == null? "0":curString);
        if (currValue > new Date().getTime()){
            //锁还未释放
            redisUtil.delKey(0, key);
        }
        //锁已经被其他线程拿到了
    }

    /**
     * GETSET 操作在设置键的值的同时，还会返回键的旧值
     * @param key
     * @return
     */
    public long getSet(String key,long seconds){
        JedisPool pool = redisUtil.getJedisPool();
        Jedis jedis = null;
        try {
            jedis = pool.getResource();
            String res = jedis.getSet(key, new Date().getTime() + seconds + "");
            return Long.valueOf(res == null? "0":res);
        }catch (Exception e){
            throw e;
        }
        finally {
            if (null != jedis){
                jedis.close();
            }
        }
    }

    /**
     * 将 key 的值设为 value ，当且仅当 key 不存在。
     若给定的 key 已经存在，则 SETNX 不做任何动作。
     * @param key
     * @return true:表示设置成功, 拿到锁了
     */
    public boolean setnx(String key, int second){
        JedisPool pool = redisUtil.getJedisPool();
        Jedis jedis = null;
        try {
            jedis = pool.getResource();
            Long res = jedis.setnx(key, new Date().getTime()+second+"");
            if (res == 0) return false;
            else return true;
        }catch (Exception e){
            throw e;
        }
        finally {
            if (null != jedis){
                jedis.close();
            }
        }
    }

    public String getString(String key){
        JedisPool pool = redisUtil.getJedisPool();
        Jedis jedis = null;
        try {
            jedis = pool.getResource();
            return jedis.get(key);
        }catch (Exception e){
            throw e;
        }
        finally {
            if (null != jedis){
                jedis.close();
            }
        }
    }
}
